%!Tex Program = xelatex
%\documentclass[a4paper]{article}
\documentclass[a4paper]{ctexart}
\usepackage{xltxtra}
\usepackage{listings}
%\setmainfont[Mapping=tex-text]{AR PL UMing CN:style=Light}
%\setmainfont[Mapping=tex-text]{AR PL UKai CN:style=Book}
%\setmainfont[Mapping=tex-text]{WenQuanYi Zen Hei:style=Regular}
%\setmainfont[Mapping=tex-text]{WenQuanYi Zen Hei Sharp:style=Regular}
%\setmainfont[Mapping=tex-text]{AR PL KaitiM GB:style=Regular} 
%\setmainfont[Mapping=tex-text]{AR PL SungtiL GB:style=Regular} 
%\setmainfont[Mapping=tex-text]{WenQuanYi Zen Hei Mono:style=Regular} 


\title{第三讲：Linux 环境和 Shell 编程}
\author{王何宇}
\date{}
\begin{document}
\maketitle
\pagestyle{empty}

\section{重定向、管道}

Shell 中一切都是文件，包括标准输入输出。比如 \verb|ls -l| 的输出结果默认是输出到 \verb|stdout|，
也就是标准输出设备，但你可以用重定向操作符将其重定向为一个文件，比如：
\begin{lstlisting}[language=sh]
ls -l > output.txt
\end{lstlisting}

这个就是把 \verb|ls -l| 的输出结果保存在 \verb|output.txt| 中。 

操作符 \verb|>| 是重写一个新文件，而 \verb|>>| 是追加在原文件末尾。
尝试一下。

我们知道除了标准输出文件 \verb|stdout|，还有一个标准错误文件 \verb|stderr|，
这个往往和 \verb|stdout| 指向同一个输出设备。但可以通过重定向分离。
一般默认的输出是标准输出文件，而 2 则值标准错误文件。比如：
\begin{lstlisting}[language=sh]
kill -HUP 1234
\end{lstlisting}
删除进程 1234, 显示
\begin{lstlisting}[language=sh]
bash: kill: (1234) - No such process
\end{lstlisting}
因为一般不会正好有这个进程号。这里全部都是标准错误文件输出的，所以如果运行
\begin{lstlisting}[language=sh]
kill -HUP 1234 > output.txt
\end{lstlisting}
则得到一个空文本文件。要
\begin{lstlisting}[language=sh]
kill -HUP 1234 2> output.txt
\end{lstlisting}
才能截获错误信息到文本文件。如果我们想同时截获标准输出和错误输出并放在一个文件里，可以
\begin{lstlisting}[language=sh]
kill -HUP 1234 >killout.txt 2>&1
\end{lstlisting}
注意，\verb|2>&1| 之间不能有空格。如果输出中有很多垃圾信息你不想看，
可以将其重定向到一个垃圾桶：
\begin{lstlisting}[language=sh]
kill -l 1234 2>/dev/null
\end{lstlisting}

既然标准输出可以重定向，标准输入自然也可以
\begin{lstlisting}[language=sh]
ls ~ -lR > output.txt
\end{lstlisting}
先搞一个大一点的输出，把它丢给 \verb|more| 做输入，功能是把输入一屏幕一屏幕输出。
\begin{lstlisting}[language=sh]
more < output.txt
\end{lstlisting}
注意这里操作符反向了。

然后这里有一件很土的事情，就是这个
\verb|output.txt| 就是一个中转站，何必一定要存下来呢？就不能直接转移过去？
也就是把一个命令的输出当作另一个命令的输入。这个当然可以，就叫管道，
操作符是 \verb|||。
\begin{lstlisting}[language=sh]
ls ~ -lR | more
\end{lstlisting}
大家可能注意到我曾经在输出中只显示有关键字的部分，用的也是管道：
\begin{lstlisting}[language=sh]
ls ~ -lR | grep usr
\end{lstlisting}
管道和重定向可以根据需要多重连接。

\section{循环过程}

Shell 提供了表示循环和分支判定的结构，如：
\begin{lstlisting}[language=sh]
for file in *
do
if grep -l figure $file
then
ls $file -l
fi
done
\end{lstlisting}
这里
\begin{lstlisting}[language=sh]
for file in *
do
...something...
done
\end{lstlisting}
是一个循环结构，\verb|file| 是一个用户定义的变量，
\verb|*| 表示当前目录下所有文件，\verb|in| 表示遍历。
所以这段命令的意思就是遍历当前目录下所有文件，对每个文件重复做
\verb|...something...| 这件事。比如：
\begin{lstlisting}[language=sh]
for fAd in *
do
echo $fAd
done
\end{lstlisting}
就是遍历当前目录，将全部文件名都打印一遍。命令 \verb|echo| 是打印输出的意思。

\section{分支过程}
然后看内层是一个分支结构：
\begin{lstlisting}[language=sh]
if [ ...condition... ]
then
...do something...
fi
\end{lstlisting}
以上是一个标准的 \verb|if ... fi| 结构。但我们用的时候没有
\verb|...condition...|，类似 C 语言，只要 \verb|...condition...| 部分不为空，
我们就认为条件是真。所以这里的意思是
\begin{lstlisting}[language=sh]
if grep -l figure $file
then
ls $file -l
fi
\end{lstlisting}
只要
\begin{lstlisting}[language=sh]
grep -l figure $file
\end{lstlisting}
过滤的结果不为空，即之前遍历到的 \verb|$file| 这些文件里存在包含 \verb|figure|
单词的，就把它的详细信息列出来。你自己可以根据需要做自由的调整，比如：
\begin{lstlisting}[language=sh]
for file in *
do
if grep -l figure $file
then
cp $file ~/Documents
fi
done
\end{lstlisting}
这是什么意思？已经具有收集功能了。

\section{通配符（wildcard expansion）}
注意到这里 \verb|*| 是一个典型的通配符，意思是全部文件；类似的通配符还有：
\begin{itemize}
\item \verb|?|，任一单个字符，比如：\verb|lec0?|，那么
  \verb|lec01|，\verb|lec02| 和 \verb|lec0k| 都匹配；
\verb|[]|，括号中任一字母。比如：\verb|lec0[abc]|，那么
\verb|lec0a|，\verb|lec0b| 和 \verb|lec0c| 都匹配；
\item \verb|[^]| 或 \verb|[!]|, 不含括号内的任一字符，比如：\verb|lec0[!12]|，
  那么 \verb|lec01| 和 \verb|lec02| 都不匹配；
\item \verb|{}|，包含大括号内任一字串，比如：\verb|lec03{.log,.pdf}| 则匹配
  \verb|lec03.pdf| 或 \verb|lec03.log|。
\end{itemize}
以上通配符可自由组合，比如：\verb|*0?.{t??,a*}|。

然后 \verb|$| 操作符主要有两个意思，要么是表示将一个变量替换成变量值，如之前的 \verb|$file|；
也可以表示对命令的替换。比如：
\begin{lstlisting}[language=sh]
grep -l crazyfish *
\end{lstlisting}
表示列出目录下所有含有 \verb|crazyfish| 字符串的文件名，那么
\begin{lstlisting}[language=sh]
ls -l $(grep -l crazyfish *)
\end{lstlisting}
就表示进一步列出这些文件的详细信息。

\section{脚本代码 (script)}

现在我们可以用连续的命令加上流程控制加上通配符实现比较复杂的功能。
这些命令可以形成一个脚本，并用 bash 去运行。具体做法是依次写下命令，
然后在第一行写上 \verb|#!/bin/bash| 表示这个脚本用哪个 shell 执行。
下面是一个完整的脚本的例子：
\begin{lstlisting}[language=sh]
#!/bin/sh
# first
# This file looks through all the files in the current
# directory for the string POSIX, and then prints the names of
# those files to the standard output.
for file in *
do
if grep -q crazyfish $file 
then
echo $file 
fi
done
exit 0
\end{lstlisting}
将它存成一个文本文件，比如叫 \verb|test.sh|，然后将其设置成可执行，
然后就可以像运行一个命令一样运行它。实际上，它构成一个组合命令，或称批处理。
这里 \verb|grep -q| 表示安静地运行，或者说，不要实际输出，因为我们这里只要求
\verb|grep| 返回是否找到这样一个状态就行。

\section{变量}

既然可以写脚本了，变量自然很重要。这里变量直接使用就可以：
\begin{lstlisting}[language=sh]
salutation=Hello
echo $salutation
\end{lstlisting}
一赋值一输出。默认就是字符串，引不引号无所谓：
\begin{lstlisting}[language=sh]
salutation="Yes Dear"
echo $salutation
\end{lstlisting}
就算是给数字，还是字符串：
\begin{lstlisting}[language=sh]
salutation=7+5
echo $salutation
\end{lstlisting}

命令 \verb|read| 可以由用户给变量赋值：
\begin{lstlisting}[language=sh]
read salutation
echo $salutation
\end{lstlisting}

从上面的过程我们可以看到，在 shell 中，双引号和不加引号都是字符串，
而单引号和 \verb|\| 才是真正的引号：
\begin{lstlisting}[language=sh]
#!/bin/sh
myvar="Hi there"
echo $myvar
echo "$myvar"
echo '$myvar'
echo \$myvar
echo Enter some text
read myvar
echo '$myvar' now equals $myvar
exit 0
\end{lstlisting}
(参见：example/scripts/quoting.sh)

\section{环境变量}

除了我们自定义的变量，shell 还内置了很多环境变量，供我们和系统交互。
\begin{itemize}
\item \verb|$HOME|，用户目录；
\item \verb|$PATH|，执行程序的路径，放在这些路径下的可执行文件能够直接被运行；
\item \verb|$PS1|，命令行提示符，普通用户是 \verb|$|，超级用户是 \verb|#|；
\item \verb|$PS2|，第二提示符，比如等待输入状态，通常是 \verb|>|；
\item \verb|$IFS|，输入参数的间隔符，一般是一个空格，也可以是 TAB 或换行；
\end{itemize}
以上都可以用 \verb|echo| 输出查看。但是
\begin{lstlisting}[language=sh]
echo $PS1
\end{lstlisting}
会得到
\begin{lstlisting}[language=sh]
${debian_chroot:+($debian_chroot)}\u@\h:\w\$
\end{lstlisting}
这里转义字符 \verb|\u| 表示用户名，\verb|\h| 表示主机名，\verb|\w| 表示当前路径。
以下环境变量专门给 scripts 使用，
\begin{itemize}
\item \verb|$0|，脚本名；
\item \verb|$#|，传递给脚本的参数个数；
\item \verb|$$|，脚本的进程编号；
\item \verb|$1|，\verb|$2|，...，传递给脚本的第 1 个，第 2 个参数，...；
\item \verb|$*|，传递给脚本的全部参数，作为一个字符串；
\item \verb|$@|，传递给脚本的全部参数，有几个参数就是几个字符串；
\end{itemize}
测试脚本(try\_var.sh)
\begin{lstlisting}[language=sh]
#!/bin/bash

echo “The program $0 is now running”
echo “The first parameter was $1”
echo “The second parameter was $2”
echo “The third parameter was $3”
echo “The all parameters were $*“

echo “The parameter list was:“
for para in $@
do
echo $para
done

exit 0
\end{lstlisting}
体会一下 \verb|@*| 和 \verb|$@| 的区别。

\section{判断条件（condition）}

实际上，正经的 \verb|if| 后面应该跟一个条件判断，条件的写法是：
\begin{lstlisting}[language=sh]
if ...condition...
\end{lstlisting}
这里 \verb|...condition...| 的有两种表达，\verb|test| 或者 \verb|[  ]|，
后者方括号两端必须各有一个空格。

\begin{table}[p!]
\caption{字符串逻辑运算关系表}
\centering
\begin{tabular}{|c|c|}
\hline
字符串比较 & 结果 \\
\hline
string1 = string2 & True 如果字符串相等 \\
\hline
string1 != string2 & True 如果字符串不想等 \\
\hline
-n string & True 如果字符串非空 \\
\hline
-z string & True 如果字符串空 \\
\hline
\end{tabular}
\end{table}

\begin{table}[p!]
  
\caption{算术逻辑运算关系表}
\centering
\begin{tabular}{|c|c|}
\hline
算术表达式 & 结果 \\
\hline
expression1 -eq expression2 & True 如果表达式值相等\\
\hline
expression1 -ne expression2 & True 如果表达式值不想等\\
\hline
expression1 -gt expression2 & True 如果 expression1 大于 expression2\\
\hline
expression1 -ge expression2 & True 如果 expression1 大于等于 expression2\\
\hline
expression1 -lt expression2 & True 如果 expression1 小于 expression2\\
\hline
expression1 -le expression2 & True 如果 expression1 小于等于 expression2\\
\hline
! expression & True 如果 expression false, 或反之\\
\hline
\end{tabular}
\end{table}

\begin{table}[p!]
  \caption{文件逻辑运算关系表}
\centering
\begin{tabular}{|c|c|}
\hline
文件判断 & 结果 \\
\hline
-d file & True 如果 file 是目录\\
\hline
-e file & True 如果 file 存在\\
\hline
-f file & True 如果 file 是正经的文件\\
\hline
-g file & True 如果 file 设置了 group id\\
\hline
-r file & True 如果 file 可读\\
\hline
-s file & True 如果 file 大小不是零\\
\hline
-u file & True 如果 file 设置了 user id\\
\hline
-w file & True 如果 file 可写\\
\hline
-x file & True 如果 file 可执行\\
\hline
\end{tabular}
\end{table}



\bibliographystyle{plain}
\bibliography{crazyfish.bib}

\end{document}
